package com.msgc.utils.crypto.aes.impl;

import com.msgc.utils.crypto.aes.AesCryptoAble;
import com.msgc.utils.crypto.aes.AesUtil;
import com.msgc.utils.crypto.aes.dao.IAesInfoDao;
import com.msgc.utils.crypto.aes.entity.AesInfoEntity;
import com.msgc.utils.crypto.aes.exception.AesCryptoException;
import com.msgc.utils.crypto.common.ByteUtil;
import com.msgc.utils.crypto.hash.Sha256Utils;
import com.msgc.utils.crypto.util.StringUtilsExt;
import lombok.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Base64;
import java.util.Date;
import java.util.UUID;

/**
 * 本地数据加解密
 *
 * 加解密算法 AES256，类型：AES/CBC/PKCS5Padding. 
 *
 * 	<p> 理论：
 * 		加解密：将 dataKey作为秘钥, dataIv作为偏移向量，使用 AES256 算法将敏感数据 data 加解密。即由本类职责。
 * 		由上可知，加解密依赖 dataKey 和 dataIv	，因此需要保证每次启动时可以拿到 dataKey 与 dataIv，且dataKey必须被保护。
 * 	 dataKey 与 dataIv 的存取方式：该职责由 {@link IAesInfoDao} 和 {@link AesInfoEntity}负责实现。
 * 		数据秘钥 dataKey 的保护：Aes256(dataKey, SHA256(rootKey, random), rootKeyIv)，保护方式同敏感数据，由另一个秘钥 rootKey 和一个加密向量保护，同样为
 * 		Aes256算法。
 *  rootKey、rootKeyIv 的保存一样由 {@link IAesInfoDao} 和 {@link AesInfoEntity}负责实现。
 * 		rootKey 的保护：持久化的值为 SHA256(rootKey, random) 而不是 rootKey 本身
 * 		rootKey 的生成：32个字符即 256位，详见 {@link Aes256Crypto#generateRootKey}
 *
 * 	<ul>
 * 	    <li> 敏感数据（data）：需要被加密的数据
 *
 * 		<li> 数据密钥：用于保护本地敏感信息。因为是基于AES算法，数据密钥仅在项目部署时生成一次（一个随机数），后续不能变更。因此需要持久化，由 IAesInfoDao 完成持久化。
 *
 * 		<li>根密钥：用于保护数据密钥 —— 持久化数据密钥时，通过根密钥加密数据密钥。
 *
 * 		<li>配套的向量：用于加密数据秘钥、加密敏感数据
 *
 * </ul>
 *
 * @author liuyanming 2019年9月18日16:55:50
 */
@Service
public class Aes256Crypto implements AesCryptoAble {

	private final static Logger log = LoggerFactory.getLogger(Aes256Crypto.class);

	/** 用于数据加密的轻量级缓存 */
	private EncryptionCache cache = EncryptionCache.getInstance();

	/** 秘钥持久化依赖：用于获取持久化的加密信息 */
	private final IAesInfoDao aesInfoDao;

	private static final byte[] DATA_KEY_IV = "Abc123++@$131488".getBytes(CHARSET_UTF_8);

	@Autowired
	public Aes256Crypto(IAesInfoDao aesInfoDao) {
		this.aesInfoDao = aesInfoDao;
	}

	// ======================================== 对外接口 ============================================


	@Override
	public String encrypt(@NonNull String text) throws AesCryptoException {
		ensureEncryption();
		byte[] encryptResult = AesUtil.encrypt(text.getBytes(CHARSET_UTF_8), cache.dataKey, cache.dateIv);
		return StringUtilsExt.bytesToHexString(encryptResult);
	}

	@Override
	public String decrypt(@NonNull String cipherText) throws AesCryptoException {
		ensureEncryption();
		byte[] decryptData = AesUtil.decrypt(Base64.getDecoder().decode(cipherText), cache.dataKey, cache.dateIv);
		return new String(decryptData, CHARSET_UTF_8);
	}

	/**
	 * 确保加密前所需的变量已经初始化：检查加密配件缓存是否已经初始化
	 * 若内存{@code this.cache}中秘钥信息为空：
	 * 1. 尝试从数据库拿				 （非首次启动时）
	 * 2. 若第一步没拿到则进行初始化，将必要信息保存至 DB （首次启动时）
	 */
	@Override
	public void ensureEncryption() {
		if (cache.initialized) {
			return;
		}
		synchronized (this) {
			try {
				log.info("LocalCrypto Initializing....");
				if (!loadSecurityInfo()) {
					initSecurityInfo();
				}
				log.info("LocalCrypto-init-SUCCESS!");
			} catch (Exception e) {
				log.error("LocalCrypto NOT Available!", e);
			}
		}
	}


	// ======================================== 初始化流程 ================================================

	/**
	 * 加载持久化的加密所需信息（目前从数据库），会将解密秘钥，缓存至内存
	 *
	 * @return 是否加载成功
	 */
	private boolean loadSecurityInfo() throws AesCryptoException {
		AesInfoEntity aesInfo;
		try {
			aesInfo = aesInfoDao.get();
			if (aesInfo == null) {
				log.info("LocalCrypto-load fail for load nothing. Maybe this is the app first launch.");
				return false;
			}
		} catch (Exception e) {
			log.warn("LocalCrypto-load FAIL!", e);
			return false;
		}

		String encryptDataKey = aesInfo.getDataKey();
		// 用于加密数据秘钥的 iv 向量，历史原因，写死
		byte[] dataKey = AesUtil.decrypt(StringUtilsExt.hexStringToBytes(encryptDataKey),
				generateRootKey(aesInfo.getRootKeyPart()),
				DATA_KEY_IV);
		byte[] dateIv = StringUtilsExt.hexStringToBytes(aesInfo.getIv());
		cache.init(dataKey, dateIv);
		log.info("LocalCrypto-load success!");
		return true;
	}

	/**
	 * 初始化本组件的加密信息，并保存至数据库的加密固件表中
	 * 同时会填充缓存
	 */
	private void initSecurityInfo() {
		try {
			log.info("LocalCrypto-init:Try create new LocalCrypto BaseInfo...");
			cache.init(generateDataKey(), generateDataKeyIv());
			aesInfoDao.save(generateSecurity());
			log.info("LocalCrypto-init:Create new LocalCrypto BaseInfo success!");
		} catch (Exception e) {
			log.error("LocalCrypto-init:Persistent BaseInfo Fail!", e);
		}
	}

	// ====================================== 初始化所需的数据生成算法 ===========================================

	/**
	 * 初始化 db 加密信息表
	 * <p>
	 * 向量以16进制字符串编码后持久化。
	 * 数据秘钥本质为随机数，但需要根秘钥、根加密向量的保护
	 *
	 * @return 本组件加密记录
	 */
	private AesInfoEntity generateSecurity() throws AesCryptoException {
		String rootKeyPart = StringUtilsExt.random(8);
		byte[] rootKey = generateRootKey(rootKeyPart);
		// 用于加密数据秘钥的 iv 向量，历史原因，写死
		String dbDataKey = StringUtilsExt.bytesToHexString(AesUtil.encrypt(cache.dataKey, rootKey, DATA_KEY_IV));
		String iv = StringUtilsExt.bytesToHexString(cache.dateIv);
		return new AesInfoEntity(
				UUID.randomUUID().toString(), "msgCollect",
				dbDataKey, rootKeyPart, iv,
				new Date(), "1.0"
		);
	}

	/**
	 * 生成 rootKey ： SHA256(rootKeyParts)
	 *	根密钥有4个部件组成，其中三个部件写死。另一个部件随机生成，以确保多个项目中不出现重复。
	 *	 rootKeyParts 总长度为32个字符，因为 AES256算法 要求秘钥部件为总长度等于256位(8*32)
	 * @param lastPart rootKey 的最后一部分
	 * @return rootKey
	 */
	private byte[] generateRootKey(String lastPart) {
		String[] keyParts = new String[4];
		keyParts[0] = "12345678";
		keyParts[1] = "rootPart";
		keyParts[2] = "abcdefgh";
		keyParts[3] = lastPart;
		StringBuilder sb = new StringBuilder();
		for (String part : keyParts) {
			sb.append(part);
		}
		return Sha256Utils.hash(sb.toString().getBytes(CHARSET_UTF_8));
	}

	/**
	 * 生成加密 数据密钥 的aes向量 dataIv
	 */
	private static byte[] generateDataKeyIv() {
		return ByteUtil.randomBytes(16);
	}

	/**
	 * 生成数据密钥
	 * @return 数据秘钥
	 */
	private static byte[] generateDataKey() {
		return ByteUtil.randomBytes(32);
	}


	// ======================================== 缓存 =============================================

	/**
	 * 数据秘钥缓存
	 * @author liuyanming
	 */
	private static class EncryptionCache {
		/** 是否初始化完毕 */
		private boolean initialized = false;
		/** 缓存数据秘钥 */
		private byte[] dataKey;
		/** 缓存加密敏感数据的 iv */
		private byte[] dateIv;

		private EncryptionCache() {}

		public static EncryptionCache getInstance() {
			return SingletonHolder.INSTANCE;
		}

		public void init(byte[] dataKey, byte[] dateIv) {
			this.dataKey = dataKey;
			this.dateIv = dateIv;
			this.initialized = true;
		}

		/** 懒加载且线程安全 单例模式 */
		private static class SingletonHolder {
			private static EncryptionCache INSTANCE = new EncryptionCache();

		}
	}

}
